# ===== generated souffle code =====
# On windows, wsl is used to call souffle.  There is no good way to 'find'
# souffle in wsl, so we skip this step entirely and will hard-code the call.
if(NOT WIN32)
  find_program(
    SOUFFLE souffle
    HINTS $ENV{PATH}
    DOC "souffle must be in your PATH to build disassembler.")
  if(NOT SOUFFLE)
    message(FATAL_ERROR "souffle was not found in your PATH. Unable to build.")
  endif()
  if(NOT SOUFFLE_INCLUDE_DIR)
    get_filename_component(SOUFFLE_BIN_DIR ${SOUFFLE} DIRECTORY)
    get_filename_component(SOUFFLE_INCLUDE_DIR ${SOUFFLE_BIN_DIR}/../include
                           ABSOLUTE)
    if(NOT EXISTS ${SOUFFLE_INCLUDE_DIR}/souffle)
      message(WARNING "SOUFFLE_INCLUDE_DIR not found")
      unset(SOUFFLE_INCLUDE_DIR)
    endif()
    unset(SOUFFLE_BIN_DIR)
  endif()
else()
  set(SOUFFLE wsl souffle)
endif()

set(DATALOG_BASE_SOURCES
    datalog/arch/arch.dl
    datalog/binary/elf/elf_binaries.dl
    datalog/binary/elf/exceptions.dl
    datalog/binary/elf/relocations.dl
    datalog/binary/pe/pe_binaries.dl
    datalog/binary/pe/relocations.dl
    datalog/bitmasks.dl
    datalog/boundary_value_analysis.dl
    datalog/code_inference.dl
    datalog/code_inference_postprocess.dl
    datalog/cfg.dl
    datalog/data.dl
    datalog/data_access_analysis.dl
    datalog/empty_range.dl
    datalog/basic_function_inference.dl
    datalog/jump_tables.dl
    datalog/main.dl
    datalog/pointer_reatribution.dl
    datalog/register_type_analysis.dl
    datalog/relative_jump_tables.dl
    datalog/symbols.dl
    datalog/symbolization.dl
    datalog/use_def_analysis.dl
    datalog/value_analysis.dl
    datalog/debug_stats.dl
    datalog/self_diagnose.dl)

set(SOUFFLE_DATALOG_DIR ${CMAKE_CURRENT_SOURCE_DIR}/datalog/)

if(DDISASM_ARM_64)
  add_definitions(-DDDISASM_ARM_64)

  set(DATALOG_ARM64_SOURCES
      datalog/arch/arm64/arch_arm64.dl
      datalog/arch/arm64/float_operations.dl
      datalog/arch/arm64/jump_operations.dl
      datalog/arch/arm64/registers.dl
      datalog/arm_binaries.dl
      datalog/arm64_binaries.dl
      datalog/arch/arm64/memory_access.dl)

  set(GENERATED_ARM64_CPP souffle_disasm_arm64.cpp)

  if(WIN32)
    set(GENERATED_ARM64_CPP_PATH
        "$$(wslpath ${CMAKE_BINARY_DIR}/src/souffle_disasm_arm64.cpp)")
  else()
    set(GENERATED_ARM64_CPP_PATH
        "${CMAKE_BINARY_DIR}/src/souffle_disasm_arm64.cpp")
  endif()

  add_custom_command(
    OUTPUT ${GENERATED_ARM64_CPP}
           # Souffle includes the path of the output file in the generated
           # program name. Change directory and use a relative path so the name
           # does not depend on build location.
    WORKING_DIRECTORY "${SOUFFLE_DATALOG_DIR}"
    COMMAND ${SOUFFLE} main.dl -g ${GENERATED_ARM64_CPP_PATH} -jauto
            -MARCH_ARM64
    DEPENDS ${DATALOG_BASE_SOURCES} ${DATALOG_ARM64_SOURCES})
endif()

if(DDISASM_X86_32)
  add_definitions(-DDDISASM_X86_32)

  set(DATALOG_X86_32_SOURCES
      datalog/arch/intel/arch_x86_32.dl
      datalog/arch/intel/float_operations.dl
      datalog/arch/intel/jump_operations.dl
      datalog/arch/intel/registers_x86_32.dl
      datalog/arch/intel/arch_x86.dl
      datalog/arch/intel/memory_access.dl)

  set(GENERATED_X86_32_CPP souffle_disasm_x86_32.cpp)

  if(WIN32)
    set(GENERATED_X86_32_CPP_PATH
        "$$(wslpath ${CMAKE_BINARY_DIR}/src/souffle_disasm_x86_32.cpp)")
  else()
    set(GENERATED_X86_32_CPP_PATH
        "${CMAKE_BINARY_DIR}/src/souffle_disasm_x86_32.cpp")
  endif()

  add_custom_command(
    OUTPUT ${GENERATED_X86_32_CPP}
           # Souffle includes the path of the output file in the generated
           # program name. Change directory and use a relative path so the name
           # does not depend on build location.
    WORKING_DIRECTORY "${SOUFFLE_DATALOG_DIR}"
    COMMAND ${SOUFFLE} main.dl -g ${GENERATED_X86_32_CPP_PATH} -jauto
            -MARCH_IA32
    DEPENDS ${DATALOG_BASE_SOURCES} ${DATALOG_X86_32_SOURCES})
endif()

if(DDISASM_X86_64)
  add_definitions(-DDDISASM_X86_64)

  set(DATALOG_X86_64_SOURCES
      datalog/arch/intel/arch_x86_64.dl
      datalog/arch/intel/float_operations.dl
      datalog/arch/intel/jump_operations.dl
      datalog/arch/intel/registers_x86_64.dl
      datalog/arch/intel/arch_x86.dl
      datalog/arch/intel/memory_access.dl)

  set(GENERATED_X86_64_CPP souffle_disasm_x86_64.cpp)

  if(WIN32)
    set(GENERATED_X86_64_CPP_PATH
        "$$(wslpath ${CMAKE_BINARY_DIR}/src/souffle_disasm_x86_64.cpp)")
  else()
    set(GENERATED_X86_64_CPP_PATH
        "${CMAKE_BINARY_DIR}/src/souffle_disasm_x86_64.cpp")
  endif()

  add_custom_command(
    OUTPUT ${GENERATED_X86_64_CPP}
           # Souffle includes the path of the output file in the generated
           # program name. Change directory and use a relative path so the name
           # does not depend on build location.
    WORKING_DIRECTORY "${SOUFFLE_DATALOG_DIR}"
    COMMAND ${SOUFFLE} main.dl -g ${GENERATED_X86_64_CPP_PATH} -jauto
            -MARCH_AMD64
    DEPENDS ${DATALOG_BASE_SOURCES} ${DATALOG_X86_64_SOURCES})
endif()

# determine what flags to use to specify -fopenmp.
if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
  set(OPENMP_FLAGS -fopenmp)
elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
  set(OPENMP_FLAGS -fopenmp=libgomp)
endif()

# Common settings for MSVC
function(set_common_msvc_options TARGET_NAME)

  target_compile_options(${TARGET_NAME} PRIVATE "-W4") # Sets warning level.

  # FIXME: Visual studio does not deactivate warnings so for now warnings do not
  # result in errors. target_compile_options(${TARGET_NAME} PRIVATE "-WX")

  # target_compile_options( ${TARGET_NAME} PRIVATE "-wd4127") # conditional
  # expression is constant

  # target_compile_options( ${TARGET_NAME} PRIVATE "-wd4244") # 'conversion'
  # conversion from 'type1' to 'type2', possible loss of data

  target_compile_options(${TARGET_NAME} PRIVATE "-permissive-")
  target_compile_options(${TARGET_NAME} PRIVATE "-EHsc")
  target_compile_options(${TARGET_NAME} PRIVATE "-bigobj")

  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Debug>:-D_DEBUG>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Debug>:-MDd>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Debug>:-Ob0>
  )# Disables inline expansion
  target_compile_options(
    ${TARGET_NAME} PRIVATE $<$<CONFIG:Debug>:-Od>) # Disables optimization,
                                                   # speeding compilation and
                                                   # simplifying debugging. http
                                                   # s://msdn.microsoft.com/en-
                                                   # us/library/k1ack8f1.aspx
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Debug>:-RTC1>
  )# Enables run-time error checking.
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Debug>:-Zi>
  )# Generates complete debugging information.

  target_compile_options(${TARGET_NAME}
                         PRIVATE $<$<CONFIG:RelWithDebInfo>:-D_NDEBUG>)
  target_compile_options(${TARGET_NAME}
                         PRIVATE $<$<CONFIG:RelWithDebInfo>:-DNDEBUG>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:RelWithDebInfo>:-MD>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:RelWithDebInfo>:-O2>
  )# Creates fast code.
  target_compile_options(
    ${TARGET_NAME} PRIVATE $<$<CONFIG:RelWithDebInfo>:-Ob2>) # The default
                                                             # value. Allows
                                                             # expansion of
                                                             # functions marked
                                                             # as inline,
                                                             # __inline, or
                                                             # __forceinline,
                                                             # and any other
                                                             # function that the
                                                             # compiler chooses.
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:RelWithDebInfo>:-Oi>
  )# Generates intrinsic functions.
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:RelWithDebInfo>:-Ot>
  )# Favors fast code.
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:RelWithDebInfo>:-Zi>
  )# Generates complete debugging information.

  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-D_NDEBUG>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-DNDEBUG>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-MD>)
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-O2>
  )# Creates fast code.
  target_compile_options(
    ${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-Ob2>) # The default value.
                                                      # Allows expansion of
                                                      # functions marked as
                                                      # inline, __inline, or
                                                      # __forceinline, and any
                                                      # other function that the
                                                      # compiler chooses.
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-Oi>
  )# Generates intrinsic functions.
  target_compile_options(${TARGET_NAME} PRIVATE $<$<CONFIG:Release>:-Ot>
  )# Favors fast code.

endfunction()

# Disabled warnings for souffle projects on MSVC
function(set_souffle_msvc_options TARGET_NAME)
  target_compile_definitions(${TARGET_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)
  target_compile_definitions(${TARGET_NAME} PRIVATE _CRT_NONSTDC_NO_WARNINGS)

  # From cpp generated by souffle:
  target_compile_options(
    ${TARGET_NAME} PRIVATE -wd4146) # unary minus operator applied to unsigned
                                    # type, result still unsigned
  target_compile_options(
    ${TARGET_NAME} PRIVATE -wd4189) # 'identifier' : local variable is
                                    # initialized but not referenced

  # From souffle headers
  target_compile_options(
    ${TARGET_NAME} PRIVATE -wd4267) # conversion from 'type1' to 'type2',
                                    # possible loss of data

  target_compile_options(
    ${TARGET_NAME} PRIVATE -wd4456) # declaration of 'decl' hides previous local
                                    # declaration
endfunction()

# ====== builder ===========

add_subdirectory(gtirb-builder)

# ====== decoder ===========

add_subdirectory(gtirb-decoder)

# ====== passes ============

add_subdirectory(passes)

# ===== functors ====
# Build a shared functors library for use with the interpreter. Disabled if
# BUILD_SHARED_LIBS is OFF
if(BUILD_SHARED_LIBS)
  add_library(functors SHARED Functors.cpp)
  target_link_libraries(functors gtirb)

  target_compile_definitions(functors PRIVATE -DSOUFFLE_INTERPRETER_FUNCTOR_LIB)

  if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
    target_compile_options(functors PRIVATE -Wno-unused-parameter)
  endif()

  target_include_directories(
    functors PRIVATE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)

  target_compile_options(functors PRIVATE ${OPENMP_FLAGS})

  if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
    target_compile_definitions(functors PRIVATE _CRT_SECURE_NO_WARNINGS)
    target_compile_definitions(functors PRIVATE _CRT_NONSTDC_NO_WARNINGS)
    set_common_msvc_options(functors)
  else()
    target_compile_options(functors PRIVATE -O3)
    target_compile_options(functors PRIVATE -Wall)
    target_compile_options(functors PRIVATE -Wextra -Wpointer-arith)
    target_compile_options(functors PRIVATE -Werror)
  endif()
endif()

# ==== ddisasm_datalog =====
# Build generated code as a static library. It is both used by tests, and needs
# different compilation flags than the rest of the source (because the generated
# souffle code won't build with -Wall -Werror).
add_library(
  ddisasm_datalog STATIC ${GENERATED_X86_32_CPP} ${GENERATED_X86_64_CPP}
                         ${GENERATED_ARM64_CPP})

target_compile_definitions(ddisasm_datalog PRIVATE __EMBEDDED_SOUFFLE__)
target_compile_definitions(ddisasm_datalog PRIVATE RAM_DOMAIN_SIZE=64)
target_compile_options(ddisasm_datalog PRIVATE ${OPENMP_FLAGS})
if(SOUFFLE_INCLUDE_DIR)
  target_include_directories(ddisasm_datalog SYSTEM
                             PRIVATE ${SOUFFLE_INCLUDE_DIR})
endif()

if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
  target_link_options(ddisasm_datalog PRIVATE -NODEFAULTLIB:LIBCMTD)

  set_common_msvc_options(ddisasm_datalog)

  set_souffle_msvc_options(ddisasm_datalog)
else()
  target_compile_options(ddisasm_datalog PRIVATE -O3)
  target_compile_options(ddisasm_datalog PRIVATE -Wno-parentheses-equality
                                                 -Wno-unused-parameter)
endif()

# ====== ddisasm ===========
# Build final ddisasm executable
add_executable(ddisasm Disassembler.cpp Interpreter.cpp Registration.cpp
                       Main.cpp Functors.cpp)

if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
  target_compile_options(ddisasm PRIVATE -Wno-unused-parameter)
endif()

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Version.h.in"
               "${CMAKE_BINARY_DIR}/include/Version.h" @ONLY)
target_include_directories(
  ddisasm PRIVATE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)
if(ehp_INCLUDE_DIR)
  target_include_directories(ddisasm PRIVATE ${ehp_INCLUDE_DIR})
endif()
if(CAPSTONE_INCLUDE_DIR)
  target_include_directories(ddisasm PRIVATE ${CAPSTONE_INCLUDE_DIR})
endif()
if(SOUFFLE_INCLUDE_DIR)
  target_include_directories(ddisasm SYSTEM PRIVATE ${SOUFFLE_INCLUDE_DIR})
endif()

target_compile_definitions(ddisasm PRIVATE __EMBEDDED_SOUFFLE__)
target_compile_definitions(ddisasm PRIVATE RAM_DOMAIN_SIZE=64)
target_compile_options(ddisasm PRIVATE ${OPENMP_FLAGS})

if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
  target_compile_definitions(ddisasm PRIVATE _CRT_SECURE_NO_WARNINGS)
  target_compile_definitions(ddisasm PRIVATE _CRT_NONSTDC_NO_WARNINGS)

  set_msvc_lief_options(ddisasm)
  set_common_msvc_options(ddisasm)
else()
  target_compile_options(ddisasm PRIVATE -O3)
  target_compile_options(ddisasm PRIVATE -Wall)
  target_compile_options(ddisasm PRIVATE -Wextra -Wpointer-arith)
  target_compile_options(ddisasm PRIVATE -Werror)
endif()

if(${GTIRB_USE_SYSTEM_BOOST} MATCHES "OFF")
  add_dependencies(ddisasm Boost)
endif()

if(DDISASM_STATIC_DRIVERS)
  if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
    # We do not want to statically link in the STL here, as MSVC is a bit
    # prickly about STL ABIs.
  else()
    target_link_libraries(ddisasm PRIVATE -static-libstdc++ -static-libgcc)
  endif()
endif()

if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
  target_link_libraries(ddisasm PRIVATE ddisasm_datalog scc_pass no_return_pass
                                        function_inference_pass)
  target_link_options(
    ddisasm PRIVATE /WHOLEARCHIVE:ddisasm_datalog$<$<CONFIG:Debug>:d>
    /WHOLEARCHIVE:no_return_pass$<$<CONFIG:Debug>:d>
    /WHOLEARCHIVE:function_inference_pass$<$<CONFIG:Debug>:d>)
else()
  if(APPLE)
    target_link_libraries(
      ddisasm PRIVATE scc_pass -Wl,-all_load ddisasm_datalog no_return_pass
                      function_inference_pass -Wl,-noall_load)
  else()
    target_link_libraries(
      ddisasm
      PRIVATE scc_pass -Wl,--whole-archive ddisasm_datalog no_return_pass
              function_inference_pass -Wl,--no-whole-archive)
  endif()
endif()

if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
  if(DDISASM_STATIC_DRIVERS)
    target_link_libraries(ddisasm PRIVATE -l:libgomp.a)
  else()
    target_link_libraries(ddisasm PRIVATE gomp)
  endif()
endif()

target_link_libraries(
  ddisasm
  PRIVATE ddisasm_datalog
          gtirb
          gtirb_pprinter
          gtirb_builder
          gtirb_decoder
          ${Boost_LIBRARIES}
          ${EXPERIMENTAL_LIB}
          ${LIBCPP_ABI}
          ${DDISASM_EXTRA_LIBS})

if(DDISASM_ENABLE_TESTS)
  add_subdirectory(tests)
endif()

if(UNIX
   AND NOT CYGWIN
   AND ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" OR "${CMAKE_BUILD_TYPE}"
                                                           STREQUAL "Debug")
   AND ${DDISASM_STRIP_DEBUG_SYMBOLS})
  string(
    RANDOM
    LENGTH 32
    ALPHABET "abcdef0123456789" BUILD_ID)
  string(SUBSTRING "${BUILD_ID}" 0 2 BUILD_ID_PREFIX)
  string(SUBSTRING "${BUILD_ID}" 2 32 BUILD_ID_SUFFIX)
  target_link_libraries(ddisasm PRIVATE "-Wl,--build-id=0x${BUILD_ID}")
  add_custom_command(
    TARGET ddisasm
    POST_BUILD
    COMMAND objcopy --only-keep-debug $<TARGET_FILE:ddisasm>
            ${CMAKE_BINARY_DIR}/bin/${BUILD_ID_SUFFIX}.debug
    COMMAND objcopy --strip-debug $<TARGET_FILE:ddisasm>)
  install(
    FILES "${CMAKE_BINARY_DIR}/bin/${BUILD_ID_SUFFIX}.debug"
    COMPONENT debug-file
    DESTINATION "lib/debug/.build-id/${BUILD_ID_PREFIX}")
endif()

install(
  TARGETS ddisasm
  COMPONENT ddisasm
  DESTINATION bin)

if(BUILD_FUNINFER)
  # ===== souffle_funinfer =====

  add_executable(funinfer FunInfer.cpp)

  target_include_directories(
    funinfer PRIVATE $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>)

  if(SOUFFLE_INCLUDE_DIR)
    target_include_directories(funinfer PRIVATE ${SOUFFLE_INCLUDE_DIR})
  endif()

  if(${GTIRB_USE_SYSTEM_BOOST} MATCHES "OFF")
    add_dependencies(funinfer Boost)
  endif()

  if(DDISASM_STATIC_DRIVERS)
    if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
      # We do not want to statically link in the STL here, as MSVC is a bit
      # prickly about STL ABIs.
    else()
      target_link_libraries(funinfer PRIVATE -static-libstdc++ -static-libgcc)
    endif()
  endif()

  target_link_libraries(
    funinfer
    PRIVATE gtirb
            gtirb_decoder
            scc_pass
            ${CAPSTONE}
            ${Boost_LIBRARIES}
            ${EXPERIMENTAL_LIB}
            ${LIBCPP_ABI}
            scc_pass)

  if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
    target_link_libraries(funinfer PRIVATE scc_pass no_return_pass
                                           function_inference_pass)
    target_link_options(
      funinfer PRIVATE /WHOLEARCHIVE:no_return_pass$<$<CONFIG:Debug>:d>
      /WHOLEARCHIVE:function_inference_pass$<$<CONFIG:Debug>:d>)
  else()
    if(APPLE)
      target_link_libraries(
        funinfer PRIVATE -Wl,-all_load no_return_pass function_inference_pass
                         -Wl,-noall_load)
    else()
      target_link_libraries(
        funinfer PRIVATE -Wl,--whole-archive no_return_pass
                         function_inference_pass -Wl,--no-whole-archive)
    endif()
  endif()

  target_compile_options(funinfer PRIVATE ${OPENMP_FLAGS})

  if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)
    target_link_options(funinfer PRIVATE -NODEFAULTLIB:LIBCMTD)
    set_common_msvc_options(funinfer)
  else()
    target_compile_options(funinfer PRIVATE -O3)
    target_compile_options(funinfer PRIVATE -Wall)
    target_compile_options(funinfer PRIVATE -Wextra -Wpointer-arith)
    target_compile_options(funinfer PRIVATE -Werror)
  endif()

  if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
    if(DDISASM_STATIC_DRIVERS)
      target_link_libraries(funinfer PRIVATE -l:libgomp.a)
    else()
      target_link_libraries(funinfer PRIVATE gomp pthread)
    endif()
  endif()

  install(TARGETS funinfer DESTINATION bin)
endif()
